package org.longs.converter;

import org.longs.bean.MapBlock;
import org.longs.bean.MapHeader;
import org.longs.bean.Mask;
import org.longs.bean.TerrainBean;
import org.longs.stream.LittleAccessFile;
import org.longs.util.MapDecompression;

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class MapConverter extends BaseConverter {

    private List<MapBlock> mapBlockList;
    private List<Mask> maskList;
    private MapHeader mapHeader;

    public List<MapBlock> getMapBlockList() {
        return mapBlockList;
    }

    public void setMapBlockList(List<MapBlock> mapBlockList) {
        this.mapBlockList = mapBlockList;
    }

    public List<Mask> getMaskList() {
        return maskList;
    }

    public void setMaskList(List<Mask> maskList) {
        this.maskList = maskList;
    }

    public MapHeader getMapHeader() {
        return mapHeader;
    }

    public void setMapHeader(MapHeader mapHeader) {
        this.mapHeader = mapHeader;
    }

    public MapConverter(String filePath) {
        super(filePath);
    }

    public MapConverter(LittleAccessFile randomAccessFile) {
        super(randomAccessFile);
    }

    @Override
    public String getFlag() {
        return "0.1M";
    }

    @Override
    public void analysisBody(LittleAccessFile randomAccessFile) throws IOException {
        this.mapHeader = analysisHead(randomAccessFile);
        this.mapBlockList = analysisMapBlock(randomAccessFile);
        this.maskList = analysisMask(randomAccessFile);
    }


    public void converterBlock(MapBlock block) throws IOException {
        analysisBlock(block);
    }

    public void converterMask(Mask mask) throws IOException {
        LittleAccessFile is = getRandomAccessFile();
        is.seek(mask.getOffset());
        mask.setKeyX(is.readLittleInt());
        mask.setKeyY(is.readLittleInt());
        mask.setWidth(is.readLittleInt());
        mask.setHeight(is.readLittleInt());
        mask.setSize(is.readLittleInt());
        mask.setOffset(mask.getOffset() + 20);
    }

    public ArrayList<TerrainBean> converterTerrain() throws IOException {
        Map<Integer, Byte> map = new HashMap<>();
        if (getFlag().equals("0.1M")) {
            LittleAccessFile is = getRandomAccessFile();
            int line = (getMapHeader().getMapColNum() - 1) * 16;
            int startBlock = getMapHeader().getMapColNum() * 192;
            byte[] call;
            int count = 0;
            int key;
            int index;
            for (int y = 0; y < getMapHeader().getMapRowNum(); y++) {
                for (int x = 0; x < getMapHeader().getMapColNum(); x++) {
                    MapBlock block = getMapBlockList().get(count++);
                    is.seek(block.getOffset());
                    block.setMaskNum(is.readLittleInt());
                    if (block.getMaskNum() > 0) {
                        is.skipBytes(block.getMaskNum() * 4);
                    }
                    while (true) {
                        byte[] bytes = new byte[4];
                        is.read(bytes);
                        block.setFlag(new String(bytes));
                        block.setSize(is.readLittleInt());
                        if (block.getFlag().equals("LLEC")) {
                            call = new byte[block.getSize()];
                            is.read(call);
                            key = y * startBlock + x * 16;
                            index = 0;
                            for (int i = 1; i <= 12; i++) {
                                for (int j = 1; j <= 16; j++) {
                                    map.put(key++, call[index++]);
                                }
                                key = key + line;
                            }
                            break;
                        } else if (block.getFlag().equals("\0\0\0\0")) {
                            break;
                        } else {
                            is.skipBytes(block.getSize());
                        }
                    }
                }
            }
        }
        ArrayList<TerrainBean> list = new ArrayList<>();
        int count = 0;
        for (int y = 0; y < getMapHeader().getMapRowNum() * 12; y++) {
            for (int x = 0; x < getMapHeader().getMapColNum() * 16; x++) {
                Byte aByte = map.get(count++);
                if (aByte != 0) {
                    TerrainBean bean = new TerrainBean();
                    bean.setX(x);
                    bean.setY(y);
                    list.add(bean);
                }
            }
        }
        return list;
    }


    public void setMaskBufferedImage(Mask mask) throws IOException {
        LittleAccessFile is = getRandomAccessFile();
        is.seek(mask.getOffset());
        byte[] bytes = new byte[mask.getSize()];
        is.read(bytes);
        MapDecompression decompression = new MapDecompression(bytes);
        List<Byte> zipData = decompression.unZipData();
        if (Math.floor((mask.getWidth() + 3) / 4) * mask.getHeight() != zipData.size()) {
            return;
        }
        int index = 0;
        int bit = 0;
        int col = mask.getKeyX() % 320;
        if (mask.getKeyX() <= 0) {
            col = 0;
        }
        int imageY = mask.getKeyY() % 240;
        if (mask.getKeyY() <= 0) {
            imageY = 0;
        }
        int alpha;
        int rgb;
        int blockIndex = ((int) Math.floor(mask.getKeyY() / 240D)) * getMapHeader().getMapColNum() + (int) Math.floor(mask.getKeyX() / 320D);
        BufferedImage blockBufferedImage = getBlockBufferedImage(blockIndex);
        BufferedImage image = new BufferedImage(mask.getWidth(), mask.getHeight(), BufferedImage.TYPE_4BYTE_ABGR);
        for (int y = 0; y < mask.getHeight(); y++) {
            for (int x = 0; x < mask.getWidth(); x++) {
                alpha = ((zipData.get(index) & 0xFF) >> bit) & 3;
                rgb = blockBufferedImage.getRGB(x + col, imageY);
                alpha = (alpha == 3) ? 150 : alpha;
                rgb = (rgb & 16777215) | (alpha << 24);
                image.setRGB(x, y, rgb);
                bit = bit + 2;// 每两位代表一像素
                if (bit == 8) {
                    bit = 0;
                    index++;
                }
            }
            imageY++;
            if (blockBufferedImage.getHeight() <= imageY ) {
                if(getMapBlockList().size() > blockIndex + getMapHeader().getMapColNum())
                {
                    blockBufferedImage = getBlockBufferedImage(blockIndex = blockIndex + getMapHeader().getMapColNum());
                }
                imageY = 0;
            }
            if (bit != 0) {
                bit = 0;
                index++;
            }
        }
        mask.setBufferedImage(image);
    }

    private BufferedImage getBlockBufferedImage(int i) throws IOException {

        MapBlock mapBlock = getMapBlockList().get(i);
        analysisBlock(mapBlock);
        return mapBlock.getBufferedImage();
    }

    private byte[] repair(byte[] src, int length) {
        List<Byte> dst = new ArrayList<>();
        int s = 0;
        while (s < length && src[s] == (byte) 0xff) {
            dst.add((byte) 0xff);
            s = s + 1;
            if (src[s] == (byte) 0xD8) {
                dst.add(src[s]);
                s = s + 1;
            } else if (src[s] == (byte) 0xA0) {
                dst.remove(dst.size() - 1);
                s = s + 1;
            } else if (src[s] == (byte) 0xC0 || src[s] == (byte) 0xC4 || src[s] == (byte) 0xDB) {
                dst.add(src[s]);
                s = s + 1;
                int len = (((src[s] & 0xff) << 8) | (src[s + 1] & 0xff));
                for (int i = 0; i < len; i++) {
                    dst.add(src[s]);
                    s = s + 1;
                }
            } else if (src[s] == (byte) 0xDA) {
                dst.add((byte) 0xDA);
                dst.add((byte) 0x00);
                dst.add((byte) 0x0C);
                s = s + 1;
                int len = (((src[s] << 8) & 0xff | src[s + 1] & 0xff)) - 2;
                s = s + 2;
                for (int i = 0; i < len; i++) {
                    dst.add(src[s]);
                    s = s + 1;
                }
                dst.add((byte) 0x00);
                dst.add((byte) 0x3F);
                dst.add((byte) 0x00);
                int count = length - s;
                for (int i = 0; i < count; i++) {
                    if (src[s] == (byte) 0xFF) {
                        dst.add((byte) 0xFF);
                        dst.add((byte) 0x00);
                    } else {
                        dst.add(src[s]);
                    }
                    s = s + 1;
                }
                dst.set(dst.size() - 2, (byte) 0xD9);
                break;
            }
        }
        byte[] bytes = new byte[dst.size()];
        for (int i = 0; i < dst.size(); i++) {
//            System.out.println(dst.get(i) & 0xff);
            bytes[i] = dst.get(i);
        }
        return bytes;
    }

    private void analysisBlock(MapBlock block) throws IOException {
        LittleAccessFile is = getRandomAccessFile();
        is.seek(block.getOffset());
        block.setMaskNum(is.readLittleInt());
        if (block.getMaskNum() > 0) {
            is.skipBytes(block.getMaskNum() * 4);
        }
        byte[] bytes = new byte[4];
        is.read(bytes);
        block.setFlag(new String(bytes));
        block.setSize(is.readLittleInt());
        if (block.getFlag().equals("GEPJ")) {
            byte[] list = new byte[block.getSize()];
            is.read(list);
            block.setBufferedImage(ImageIO.read(new ByteArrayInputStream(repair(list, block.getSize()))));
        } else {
            bytes = new byte[block.getSize()];
            is.read(bytes);
            BufferedImage read = ImageIO.read(new ByteArrayInputStream(bytes));
            block.setBufferedImage(read);
        }
    }

    private List<Mask> analysisMask(LittleAccessFile is) throws IOException {
        is.seek(is.readLittleInt());
        List<Mask> maskList = new ArrayList<>();
        int maskNum = is.readLittleInt();
        for (int i = 0; i < maskNum; i++) {
            Mask mask = new Mask();
            mask.setOffset(is.readLittleInt());
            maskList.add(mask);
        }
        return maskList;
    }

    private List<MapBlock> analysisMapBlock(LittleAccessFile is) throws IOException {
        ArrayList<MapBlock> mapBlockList = new ArrayList<>(this.mapHeader.getMapNum());
        for (int row = 0; row < this.mapHeader.getMapRowNum(); row++) {
            for (int col = 0; col < this.mapHeader.getMapColNum(); col++) {
                MapBlock mapBlock = new MapBlock();
                mapBlock.setOffset(is.readLittleInt());
                mapBlock.setCol(col);
                mapBlock.setRow(row);
                mapBlockList.add(mapBlock);
            }
        }
        return mapBlockList;
    }

    private MapHeader analysisHead(LittleAccessFile is) throws IOException {
        MapHeader headerBean = new MapHeader();
        headerBean.setFlag(getFlag());
        headerBean.setCol(320);
        headerBean.setRow(240);
        headerBean.setWidth(is.readLittleInt());
        headerBean.setHeight(is.readLittleInt());
        headerBean.setMapRowNum((int) Math.ceil(headerBean.getHeight() / 240D));
        headerBean.setMapColNum((int) Math.ceil(headerBean.getWidth() / 320D));
        headerBean.setMapNum(headerBean.getMapColNum() * headerBean.getMapRowNum());
        return headerBean;
    }
}
